package com.quanyan.ic.vendingMachine.protocol.server.handler;

import com.quanyan.ic.vendingMachine.dao.VmPlaceMappingDAO;
import com.quanyan.ic.vendingMachine.model.VmPlaceMapping;
import com.quanyan.ic.vendingMachine.protocol.Cmd;
import com.quanyan.ic.vendingMachine.protocol.Code;
import com.quanyan.ic.vendingMachine.protocol.codec.protobuf.VmMessageBuilder;
import com.quanyan.ic.vendingMachine.protocol.codec.protobuf.VmMessageProto;
import com.quanyan.ic.vendingMachine.protocol.server.session.Session;
import com.quanyan.ic.vendingMachine.protocol.server.session.SessionManager;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * Created by zhuo.shi on 2017/7/31.
 * 售货机客户端通讯握手处理器，保存客户端session到缓存
 */
@Component("HandShakeRespHandler")
@ChannelHandler.Sharable
public class HandShakeRespHandler extends ChannelInboundHandlerAdapter {

    private Logger logger = LoggerFactory.getLogger(HandShakeRespHandler.class);

    @Autowired
    private SessionManager sessionManager;
    @Autowired
    private VmPlaceMappingDAO vmPlaceMappingDAO;

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        Channel channel = ctx.channel();
        logger.warn("client {} disconnected, caused by {}", channel.remoteAddress(), cause.getMessage());
        //判断是否是RST包的导致的异常
        //因为4层TCP来源IP为客户端的IP，所以若是10、100开头的IP那么肯定是SLB的健康检查IP，这里为了节约篇幅直接判断是否是10或100开头，实际应用中可以详尽的根据上文中的健康检查IP来判断
        if (cause.getMessage().startsWith("Connection reset")
                && (ctx.channel().remoteAddress().toString().startsWith("/10.")
                || ctx.channel().remoteAddress().toString().startsWith("/100.") || ctx.channel().remoteAddress().toString().startsWith("/42.120.165.21"))) {
            logger.info("health check!");
            return;
        }

        //删除客户端缓存session
        sessionManager.remove(Session.build(null, ctx.channel()));

        //关闭连接
        ctx.close();
        ctx.fireExceptionCaught(cause);
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {

        VmMessageProto.VmMessage vmMessage = (VmMessageProto.VmMessage) msg;
        String vmNumber = vmMessage.getVmNumber();
        VmMessageBuilder vmMessageBuilder = null;
        if (!vmMessage.hasVmNumber() || !vmMessage.hasCmd()) {
            //握手失败，无编号
            vmMessageBuilder = new VmMessageBuilder("");
            ctx.writeAndFlush(
                    vmMessageBuilder.buildCmd(Cmd.HANDSHAKE_RESP).buildRespCode(Code.RC_301).getMessage()
            );
            logger.warn("handshake failed, the vmNumber or cmd is null!");
            return;
        }
        if (vmMessage.getCmd() != Cmd.HANDSHAKE_REQ.getValue()) {  //其它消息类型，转交下一个handler
            ctx.fireChannelRead(msg);
            return;
        }
        VmPlaceMapping mapping = vmPlaceMappingDAO.findByVmNumber(vmNumber);
        if (mapping == null || !mapping.getInUse()) {        //售货机没有安装或者禁用
            vmMessageBuilder = new VmMessageBuilder(vmNumber);
            ctx.writeAndFlush(
                    vmMessageBuilder.buildCmd(Cmd.HANDSHAKE_RESP).buildRespCode(Code.RC_302).getMessage()
            );
            logger.warn("handshake failed, the vmNumber={} machine mapping is null or disable", vmNumber);
            return;
        }
        logger.info("received cmd {}, msg=\n{} \nfrom client {}", Cmd.HANDSHAKE_REQ.getDesc(), vmMessage, ctx.channel().remoteAddress());
        //保存客户端session
        Session session = Session.build(vmMessage.getVmNumber(), ctx.channel());  //售货机编号作为session id
        sessionManager.save(session);
        //应答客户端握手成功
        vmMessageBuilder = new VmMessageBuilder(vmMessage.getVmNumber());
        ctx.writeAndFlush(
                vmMessageBuilder.buildCmd(Cmd.HANDSHAKE_RESP).buildRespCode(Code.RC_200).getMessage()
        );
        logger.info("handshake success of client {}", ctx.channel().remoteAddress());
    }
}
